import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.IOUtils;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.HunkHeader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class Test {

    private static final String branch_dev = "remotes/origin/dev";
    private static final String branch_main = "remotes/origin/main";
    private static final String branch_master = "remotes/origin/master";

    private static final String basePath = "C:\\projects\\baiwei\\";

    @SuppressWarnings("unused")
    public static void main(String[] args) throws IOException, GitAPIException {
        //1. 从github上拉最新的项目
        cloneAndFetchProjects();
        //2. 遍历项目进行统计
//        List<AuthorStats> statsList = getMultiRepos();

        //3. 输出结果
//        System.out.println("总共的提交记录统计=>" + JSONUtil.toJsonStr(statsList));
    }

    private static void cloneAndFetchProjects() throws IOException {
        //1. 获取abi所有的项目的git地址
        List<GitRepo> repos = getAllRepos();
        //2. 执行拉取
        repos.forEach(repo -> gitCloneOrPullOfProjects(repo));
    }

    private static void gitCloneOrPullOfProjects(GitRepo repo) {
        File directory = new File(basePath + "\\" + repo.getName());
        Git gitResult = null;
        if (directory.exists()) {
            try {
                Git git = Git.open(new File(basePath + repo.getName()));
                PullResult pullResult = git.pull()
//                        .setCredentialsProvider(new UsernamePasswordCredentialsProvider("704373316@qq.com", "fyd8305263"))
                        .call();
                System.out.println("git pull["+ repo.getName() +"] result: " + pullResult.isSuccessful());
            } catch (GitAPIException | IOException e) {
                e.printStackTrace();
                gitResult.close();
            }
        } else {
            String remoteUri = repo.getSshUrl();
            CloneCommand command = Git.cloneRepository();
            command.setDirectory(directory);
            command.setURI(remoteUri);
            try {
                gitResult = command.call();
                System.out.println();
            } catch (GitAPIException e) {
                e.printStackTrace();
                gitResult.close();
            }
        }

//        ExecutorService service = Executors.newFixedThreadPool(10);
//        service.submit(new Runnable() {
//            @Override
//            public void run() {
//
//            }
//        });
    }

    private static List<GitRepo> getAllRepos() throws IOException {
        String url = "https://api.github.com/orgs/ab-inbev-apac/repos";
        OkHttpClient client = new OkHttpClient();

        final Request request = new Request.Builder()
                .url(url)
                .addHeader("Accept", "application/vnd.github.v3+json")
                .addHeader("Authorization", "Basic eXVkYWZ1MTk4ODpnaHBfUU85ZER5RHVJVWZJcERER3NROVM3blpFOWJyM2syMXlXZzRo")
                .build();

        Response response = null;
        try {
            response = client.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String repoStr = new String(response.body().bytes(), "UTF-8");

        return MyJsonUtils.nativeRead(repoStr, new TypeReference<List<GitRepo>>() {
        });
    }

    private static Map<String, Integer> getSingleRepo() throws IOException {
        Map<String, CodeLineStats> totalCodeLineCountMap = new TreeMap<>();
        return getContributorCommits("C:\\opensource_projects\\test-init-jgit", totalCodeLineCountMap);
    }

    private static List<AuthorStats> getMultiRepos() throws IOException {
        File baseDir = new File("C:\\projects\\");
        File[] files = baseDir.listFiles();
        File tmpFile = null;

        List<AuthorStats> list = new ArrayList<>();

        Map<String, Integer> totalCommitCountMap = new TreeMap<>();
        Map<String, CodeLineStats> totalCodeLineCountMap = new TreeMap<>();

        for (int i = 0; i < files.length; i++) {
            tmpFile = files[i];
            if (tmpFile.isDirectory()) {
                Map<String, Integer> tmpMap = getContributorCommits(tmpFile.getName(), totalCodeLineCountMap);
                Set<Map.Entry<String, Integer>> set = tmpMap.entrySet();
                set.forEach(entry -> {
                    Integer totalCount = totalCommitCountMap.get(entry.getKey());
                    if (totalCount == null) {
                        totalCommitCountMap.put(entry.getKey(), entry.getValue());
                    } else {
                        totalCommitCountMap.put(entry.getKey(), totalCount + entry.getValue());
                    }
                });
            }
        }


        Set<Map.Entry<String, Integer>> sets = totalCommitCountMap.entrySet();
        for (Map.Entry<String, Integer> objMap : sets) {
            String authorName = objMap.getKey();
            AuthorStats authorStats = new AuthorStats();
            authorStats.setAuthor(authorName);
            CodeLineStats codeLineStats = totalCodeLineCountMap.get(authorName);
            authorStats.setCodeLineStats(codeLineStats);
            authorStats.setCommitCounts(objMap.getValue());
            list.add(authorStats);
        }
        return list;
    }

    private List<String> getCommitIds(Git git, Repository repository) throws IOException, GitAPIException {
        Iterable<RevCommit> logs = git.log()
                .add(repository.resolve(branch_dev))
                .call();
        int count = 0;
        List<String> commitIdList = new ArrayList<>();
        for (RevCommit rev : logs) {
            commitIdList.add(rev.getName());
            System.out.println("Commit: " + rev /* + ", name: " + rev.getName() + ", id: " + rev.getId().getName() */);
            count++;
        }
        return commitIdList;
    }

    private static Map<String, Integer> getContributorCommits(String projectName, Map<String, CodeLineStats> totalCodeLineCountMap) throws IOException {
        System.out.println("====================项目[" + projectName + "]====================");
        Repository repository = new FileRepositoryBuilder()
                .setGitDir(new File(basePath + projectName + "\\.git"))
                .build();

        RevWalk revWalk = null;

        Map<String, Integer> commitCountMap = new TreeMap<>();
        try {
            revWalk = new RevWalk(repository);
            revWalk.setRevFilter(RevFilter.NO_MERGES);
            ObjectId commitId = repository.resolve(branch_master);

            if (commitId == null) {
                System.out.println("使用main分支");
                commitId = repository.resolve(branch_main);
            }
            revWalk.markStart(revWalk.parseCommit(commitId));

            for (RevCommit commit : revWalk) {
                Map<String, Integer> totalLineMap = getTotalLines(commit, repository);
                System.out.println("该[" + commit.getName() + "]提交，增加行数:"
                        + totalLineMap.get("totalAddedLineCount") +
                        ", 移除行数:" + totalLineMap.get("totalSubLinesCount") + "\n\n");

                String authorName = commit.getCommitterIdent().getName();
                Integer commitCountINT = commitCountMap.get(authorName);
                if (commitCountINT == null) {
                    commitCountMap.put(commit.getCommitterIdent().getName(), 1);
                } else {
                    commitCountMap.put(commit.getCommitterIdent().getName(), commitCountINT.intValue() + 1);
                }

                CodeLineStats codeLineStats = totalCodeLineCountMap.get(authorName);
                if (codeLineStats == null) {
                    codeLineStats = new CodeLineStats(totalLineMap.get("totalAddedLineCount"), totalLineMap.get("totalSubLinesCount"));
                } else {
                    codeLineStats.setAddedCount(codeLineStats.getAddedCount() + totalLineMap.get("totalAddedLineCount"));
                    codeLineStats.setRemovedCount(codeLineStats.getRemovedCount() + totalLineMap.get("totalSubLinesCount"));
                }
                totalCodeLineCountMap.put(authorName, codeLineStats);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            revWalk.dispose();
            System.out.println("commitCountMap=>" + JSONUtil.toJsonStr(commitCountMap));
            return commitCountMap;
        }
    }

    private static Map<String, Integer> getTotalLines(RevCommit verCommit, Repository repository) throws IOException {
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        System.out.println("commit文案:" + verCommit.getShortMessage());
        System.out.println("提交人:" + verCommit.getAuthorIdent().getName());
        String newCommitId = verCommit.getName();
        System.out.println("newCommitId: " + newCommitId); //如果通过Tag定位，可以获得其SHA-1 Hash Value
//        ObjectId newTreeId = repository.resolve(newCommitId);
        String newCommitTreeId = verCommit.getTree().getName();
        System.out.println("newCommitTreeId: " + newCommitTreeId);

        ObjectId newTreeId = repository.resolve(newCommitTreeId);
        int parentCount = verCommit.getParentCount();
        System.out.println("parentCount=>" + parentCount);

        int totalAddedLineCount = 0;
        int totalSubLinesCount = 0;

        if (parentCount > 0) {
            String prevCommitTreeId = verCommit.getParent(0).getTree().getName();
            System.out.println("prevCommitId: " + verCommit.getParent(0).getName());
            System.out.println("prevCommitTreeId: " + prevCommitTreeId);
            ObjectId oldTreeId = repository.resolve(prevCommitTreeId);

            List<DiffEntry> diffFix = getChangedFileList(repository, oldTreeId, newTreeId);//获取变更的文件列表

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            DiffFormatter df = new DiffFormatter(out);
//       df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);//如果加上这句，就是在比较的时候不计算空格，WS的意思是White Space
            df.setRepository(repository);

            System.out.println("变更文件:");//变更文件的路径

            for (DiffEntry entry : diffFix) {

                df.format(entry);
                String diffText = out.toString("UTF-8");
                System.out.println(entry.getNewPath());
                int addSize = 0;
                int subSize = 0;
                FileHeader fileHeader = df.toFileHeader(entry);
                List<HunkHeader> hunks = (List<HunkHeader>) fileHeader.getHunks();
                for (HunkHeader hunkHeader : hunks) {
                    EditList editList = hunkHeader.toEditList();
                    for (Edit edit : editList) {
                        subSize += edit.getEndA() - edit.getBeginA();
                        addSize += edit.getEndB() - edit.getBeginB();
                    }
                }
                System.out.println("addSize=" + addSize);//增加和减少的代码行数统计，我和Git Log核对了一下，还挺准确的。
                System.out.println("subSize=" + subSize);

                totalAddedLineCount += addSize;
                totalSubLinesCount += subSize;
                out.reset();
            }
        } else {
            ObjectId newCommitObjId = repository.resolve(newCommitId);
            totalAddedLineCount += countLinesOfFileInCommit(repository, newCommitObjId, "README.md");
        }

        Map<String, Integer> lineCountsMap = new HashMap<>();
        lineCountsMap.put("totalAddedLineCount", totalAddedLineCount);
        lineCountsMap.put("totalSubLinesCount", totalSubLinesCount);
        return lineCountsMap;
    }

    static List<DiffEntry> getChangedFileList(Repository repo, ObjectId oldTreeId, ObjectId newTreeId) {
        List<DiffEntry> returnDiffs = null;

        try {

            System.out.println("Printing diff between the Revisions: " + newTreeId + " and " + oldTreeId);

            // prepare the two iterators to compute the diff between
            try (ObjectReader reader = repo.newObjectReader()) {
                CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
                oldTreeIter.reset(reader, oldTreeId);
                CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
                newTreeIter.reset(reader, newTreeId);

                // finally get the list of changed files
                try (Git git = new Git(repo)) {
                    List<DiffEntry> diffs = git.diff()
                            .setNewTree(newTreeIter)
                            .setOldTree(oldTreeIter)
                            .call();
                    for (DiffEntry entry : diffs) {
//                        System.out.println("Entry: " + entry);
                    }
                    returnDiffs = diffs;
                } catch (GitAPIException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return returnDiffs;
    }

    private static int countLinesOfFileInCommit(Repository repository, ObjectId commitID, String name) throws IOException {
        try (RevWalk revWalk = new RevWalk(repository)) {
            RevCommit commit = revWalk.parseCommit(commitID);
            RevTree tree = commit.getTree();
            System.out.println("Having tree: " + tree);

            // now try to find a specific file
            try (TreeWalk treeWalk = new TreeWalk(repository)) {
                treeWalk.addTree(tree);
                treeWalk.setRecursive(true);
//                treeWalk.setFilter(PathFilter.create(name));
                if (!treeWalk.next()) {
                    throw new IllegalStateException("Did not find expected file 'README.md'");
                }

                ObjectId objectId = treeWalk.getObjectId(0);
                ObjectLoader loader = repository.open(objectId);

                // load the content of the file into a stream
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                loader.copyTo(stream);

                revWalk.dispose();

                return IOUtils.readLines(new ByteArrayInputStream(stream.toByteArray()), "UTF-8").size();
            }
        }
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class AuthorStats {
        private String author;
        private CodeLineStats codeLineStats;
        private int commitCounts;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class CodeLineStats {
        private int addedCount;
        private int removedCount;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class GitRepo {
        private String name;

        @JsonProperty("ssh_url")
        private String sshUrl;
    }


}
